﻿Imports System.Diagnostics.Contracts
Imports AutoMapper
Imports FTTK.Exceptions
Imports FTTK.Entities
Imports FTTK.Services.EventArgs
Imports FTTK.Services.Interfaces.Rest
Imports FTTK.Services.Interfaces
Imports FTTK.Json
Imports FTTK.Utilities
Imports System.Net
Imports System.IO

Namespace FTTK.Services.Implementations.Rest
    ''' <summary>
    ''' Base class for Rest services
    ''' </summary>
    ''' <remarks></remarks>
    Public MustInherit Class BaseService
        Implements IBaseService

        Shared Sub New()
            BaseServiceMapping.DefineMapping()
        End Sub


        ''' <summary>
        ''' Initializes a new instance of the <see cref="BaseService" /> class.
        ''' </summary>
        ''' <remarks></remarks>
        'Public Sub New()
        '    Me._Requester = New RestRequester(Me._Credential)
        'End Sub

        'Public Sub New(cacheAdapter As ICacheAdapter)
        '    Me.CacheAdapter = cacheAdapter
        '    Me._Requester = New RestRequester(Me._Credential)
        'End Sub

        Public Sub New(apiKey As String, cacheAdapter As ICacheAdapter)
            Me.ApiKey = apiKey
            Me.CacheAdapter = cacheAdapter
            Me._Requester = New RestRequester(apiKey)
        End Sub


        ''' <summary>
        ''' Initializes a new instance of the <see cref="BaseService" /> class.
        ''' </summary>
        ''' <param name="apiKey">The API key.</param>
        ''' <remarks></remarks>
        Public Sub New(apiKey As String)
            Me.ApiKey = apiKey
            Me._Requester = New RestRequester(apiKey)
        End Sub


        Private _CacheAdapter As ICacheAdapter

        ''' <summary>
        ''' Gets or sets the cache adapter.
        ''' </summary>
        ''' <value>The cache adapter.</value>
        ''' <remarks></remarks>
        Public Property CacheAdapter As ICacheAdapter
            Get
                Return Me._CacheAdapter
            End Get
            Set(ByVal value As ICacheAdapter)
                Contract.Requires(Of ArgumentNullException)(value IsNot Nothing, "CacheAdapter must be not null!")
                Me._CacheAdapter = value
            End Set
        End Property

        Protected _ApiKey As String

        ''' <summary>
        ''' Gets or sets the API key.
        ''' </summary>
        ''' <value>The API key.</value>
        ''' <remarks></remarks>
        Public Property ApiKey As String
            Get
                Return _ApiKey
            End Get
            Set(ByVal value As String)
                Contract.Requires(Of ArgumentNullException)(Not String.IsNullOrEmpty(value), "ApiKey must be not null!")
                Me._ApiKey = value
                If Me._Requester isnot Nothing then Me._Requester.ApiKey = value
            End Set
        End Property

        Protected _Requester As RestRequester = Nothing
        Protected _JsonParser As IJsonParser = New JsonParser

#Region "IBaseService Interface"

#Region "Test"
#If SILVERLIGHT = 0 Then
        ''' <summary>
        ''' Tests this instance.
        ''' </summary>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Overridable Function Test() As Boolean Implements IBaseService.Test
            Dim retVal As Boolean = False
            Try
                Dim resultStr = Me._Requester.GetResponse(RequestUrlHelper.TestRequestUrl)
                Dim result = _JsonParser.ParseIntoSingleObject(resultStr)
                If result.ContainsKey("test") Then
                    retVal = (String.Compare(result.Item("test").ToString(), "SUCCESS", True) = 0)
                End If
            Catch ex As Exception
                ManageException(ex)
            End Try
            Return retVal
        End Function
#End If

        ''' <summary>
        ''' Tests the async.
        ''' </summary>
        ''' <param name="userState">State of the user.</param>
        ''' <remarks></remarks>
        Public Overridable Sub TestAsync(ByVal userState As Object) Implements IBaseService.TestAsync
            Try
                Me._Requester.GetResponseAsync(RequestUrlHelper.TestRequestUrl, New Action(Of String, Exception, Object)(AddressOf TestAsyncCallback), userState)
            Catch ex As Exception
                If TypeOf ex Is WebException Then
                    Dim webEx = CType(ex, WebException)
                    OnTestCompleted(New TestCompletedEventArgs() With {.Error = CreateErrorFromWebException(webEx),
                                                                       .Result = False,
                                                                       .UserState = userState})
                Else
                    Throw
                End If
            End Try
        End Sub

        Private Sub TestAsyncCallback(responseString As String, ex As Exception, stateObject As Object)
            If ex Is Nothing Then
                Dim result = _JsonParser.ParseIntoSingleObject(responseString)
                Dim arg As TestCompletedEventArgs
                If result.ContainsKey("test") Then
                    arg = New TestCompletedEventArgs() With {.Error = Nothing,
                                                            .UserState = stateObject,
                                                            .Result = (String.Compare(result.Item("test").ToString(), "SUCCESS", StringComparison.InvariantCultureIgnoreCase) = 0)}
                Else
                    arg = New TestCompletedEventArgs() With {.Error = Nothing,
                                                             .UserState = stateObject,
                                                            .Result = False}
                End If
                OnTestCompleted(arg)
            Else
                If TypeOf ex Is WebException Then
                    Dim webEx = CType(ex, WebException)
                    OnTestCompleted(New TestCompletedEventArgs() With {.Error = CreateErrorFromWebException(webEx),
                                                                       .Result = False,
                                                                       .UserState = stateObject})
                Else
                    Throw ex
                End If
            End If
        End Sub

        Public Event TestCompleted(ByVal sender As Object, ByVal e As TestCompletedEventArgs) Implements IBaseService.TestCompleted

        Protected Sub OnTestCompleted(ByVal e As EventArgs.TestCompletedEventArgs)
            RaiseEvent TestCompleted(Me, e)
        End Sub
#End Region

#Region "GetData"
#If SILVERLIGHT = 0 Then
        ''' <summary>
        ''' Gets the data.
        ''' </summary>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Overridable Function GetData() As FotoliaData Implements IBaseService.GetData
            Dim retVal As FotoliaData = Nothing
            Try
                retVal = ExecuteQuery(Of FotoliaData)("GetData",
                                                      Function()
                                                          Dim resultStr = Me._Requester.GetResponse(RequestUrlHelper.GetDataRequestUrl)
                                                          Dim result = _JsonParser.ParseIntoSingleObject(resultStr)
                                                          Return result
                                                      End Function,
                                                      RequestUrlHelper.GetDataRequestUrl)
            Catch ex As Exception
                ManageException(ex)
            End Try
            Return retVal
        End Function
#End If

        ''' <summary>
        ''' Gets the data async.
        ''' </summary>
        ''' <param name="userState">The user state object.</param>
        ''' <remarks></remarks>
        Public Overridable Sub GetDataAsync(ByVal userState As Object) Implements IBaseService.GetDataAsync
            Try
                Dim result = GetResultFromCache(Of FotoliaData)("GetData", RequestUrlHelper.GetDataRequestUrl)
                If result Is Nothing Then
                    Me._Requester.GetResponseAsync(RequestUrlHelper.GetDataRequestUrl, AddressOf GetDataAsyncCallback, userState)
                Else
                    OnGetDataCompleted(New GetDataCompletedEventArgs() With {.Error = Nothing,
                                                                             .Result = True,
                                                                             .FotoliaData = result,
                                                                             .UserState = userState})
                End If
            Catch ex As Exception
                If TypeOf ex Is WebException Then
                    Dim webEx = CType(ex, WebException)
                    OnGetDataCompleted(New GetDataCompletedEventArgs() With {.Error = CreateErrorFromWebException(webEx),
                                                                             .Result = False,
                                                                             .FotoliaData = Nothing,
                                                                             .UserState = userState})
                Else
                    Throw
                End If
            End Try
        End Sub

        Private Sub GetDataAsyncCallback(responseString As String, ex As Exception, stateObject As Object)
            If ex Is Nothing Then
                Dim arg As GetDataCompletedEventArgs
                Dim result = _JsonParser.ParseIntoSingleObject(responseString)
                If result IsNot Nothing Then
                    Dim fotoliaData = Mapper.Map(Of JsonObject, Entities.FotoliaData)(result)
                    SetResultInCache(Of FotoliaData)("GetData", RequestUrlHelper.GetDataRequestUrl, fotoliaData)
                    arg = New GetDataCompletedEventArgs() With {.Error = Nothing,
                                                                .UserState = stateObject,
                                                                .Result = True,
                                                                .FotoliaData = fotoliaData}
                Else
                    arg = New GetDataCompletedEventArgs() With {.Error = New [Error]() With {.Code = "", .Message = FotoliaErrorMessages.GetDefaultMessage()},
                                                                .UserState = stateObject,
                                                                .Result = False,
                                                                .FotoliaData = Nothing}

                End If
                OnGetDataCompleted(arg)
            Else
                If TypeOf ex Is WebException Then
                    Dim webEx = CType(ex, WebException)
                    OnGetDataCompleted(New GetDataCompletedEventArgs() With {.Error = CreateErrorFromWebException(webEx),
                                                                             .Result = False,
                                                                             .UserState = stateObject,
                                                                             .FotoliaData = Nothing})
                Else
                    Throw ex
                End If
            End If
        End Sub

        Public Event GetDataCompleted(ByVal sender As Object, ByVal e As GetDataCompletedEventArgs) Implements IBaseService.GetDataCompleted

        Protected Sub OnGetDataCompleted(ByVal e As EventArgs.GetDataCompletedEventArgs)
            RaiseEvent GetDataCompleted(Me, e)
        End Sub
#End Region

#End Region

        ''' <summary>
        ''' Manages the exception.
        ''' </summary>
        ''' <param name="ex">The ex.</param>
        ''' <remarks></remarks>
        Protected Sub ManageException(ByVal ex As Exception)
            If TypeOf ex Is WebException Then
                Throw CreateException(CType(ex, WebException))
            Else
                Throw ex
            End If
        End Sub

        ''' <summary>
        ''' Creates the exception.
        ''' </summary>
        ''' <param name="webException">WebException from which creates the Fotolia Exception.</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Protected Function CreateException(webException As WebException) As FotoliaException
            Contract.Requires(Of ArgumentNullException)(webException IsNot Nothing, "webException must be not null!")
            Dim exception As FotoliaException = Nothing
            Dim responseString As String
            Try
                Using dataStream = webException.Response.GetResponseStream()
                    Using reader = New StreamReader(dataStream)
                        responseString = reader.ReadToEnd()
                    End Using
                End Using
                Dim jsonObject = _JsonParser.ParseIntoSingleObject(responseString)
                If jsonObject IsNot Nothing Then
                    exception = Mapper.Map(Of JsonObject, FotoliaException)(jsonObject)
                End If
            Catch ex As Exception

            End Try
            Return exception
        End Function


        ''' <summary>
        ''' Creates the error from web exception.
        ''' </summary>
        ''' <param name="webException">The web exception.</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Protected Function CreateErrorFromWebException(webException As WebException) As FTTK.Services.Error
            Contract.Requires(Of ArgumentNullException)(webException IsNot Nothing, "webException must be not null!")
            Dim [error] As FTTK.Services.Error = Nothing
            Dim responseString As String
            Try
                Using dataStream = webException.Response.GetResponseStream()
                    Using reader = New StreamReader(dataStream)
                        responseString = reader.ReadToEnd()
                    End Using
                End Using
                Dim jsonObject = _JsonParser.ParseIntoSingleObject(responseString)
                If jsonObject IsNot Nothing Then
                    [error] = Mapper.Map(Of JsonObject, FTTK.Services.Error)(jsonObject)
                End If
            Catch ex As Exception

            End Try
            Return [error]
        End Function

#Region "Cache manager"
        ''' <summary>
        ''' Retrieves the result from cache.
        ''' </summary>
        ''' <typeparam name="T"></typeparam>	
        ''' <param name="operationName">Name of the operation.</param>
        ''' <param name="requestUrl">Request url</param>
        ''' <param name="sessionId">the Session Id</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Protected Function GetResultFromCache(Of T)(operationName As String,
                                                    requestUrl As String,
                                                    Optional sessionId As String = Nothing) As T
            Dim retval As T = Nothing
            If CacheAdapter IsNot Nothing Then
                Try
                    Dim hashKey = HashGenerator.GenerateKey(ApiKey, operationName, requestUrl, sessionId)
                    retval = CacheAdapter.GetData(Of T)(hashKey)
                Catch ex As Exception
                    retval = Nothing
                End Try
            End If
            Return retval
        End Function

        Protected Sub SetResultInCache(Of T)(operationName As String,
                                            requestUrl As String,
                                            resultValue As T)
            SetResultInCache(Of T)(operationName, requestUrl, Nothing, resultValue)
        End Sub

        ''' <summary>
        ''' Sets the result in cache.
        ''' </summary>
        ''' <typeparam name="T"></typeparam>	
        ''' <param name="operationName">Name of the operation.</param>
        ''' <param name="resultValue">The result value to store in cache.</param>
        ''' <param name="requestUrl"></param>
        ''' <param name="sessionId"></param>
        ''' <remarks></remarks>
        Protected Sub SetResultInCache(Of T)(operationName As String,
                                            requestUrl As String,
                                            sessionId As String,
                                            resultValue As T)
            If CacheAdapter IsNot Nothing Then
                Try
                    Dim hashKey = HashGenerator.GenerateKey(ApiKey, operationName, requestUrl, sessionId)
                    CacheAdapter.SetData(Of T)(hashKey, resultValue)
                Catch ex As Exception
                End Try
            End If
        End Sub
#End Region

        Protected Function ExecuteQuery(Of Tout)(operationName As String,
                                                 noCacheFunc As Func(Of JsonObject),
                                                 requestUrl As String,
                                                 Optional sessionId As String = Nothing,
                                                 Optional mappingFunc As Func(Of JsonObject, Tout) = Nothing) As Tout
            Dim retVal = GetResultFromCache(Of Tout)(operationName, requestUrl, sessionId)
            If retVal Is Nothing Then
                Dim result = noCacheFunc()
                If mappingFunc IsNot Nothing Then
                    retVal = mappingFunc(result)
                Else
                    retVal = Mapper.Map(Of JsonObject, Tout)(result)
                End If
                SetResultInCache(Of Tout)(operationName, requestUrl, sessionId, retVal)
            End If
            Return retVal
        End Function


        Protected Function ExecuteQuery(Of Tout)(operationName As String,
                                                 noCacheFunc As Func(Of IEnumerable(Of JsonObject)),
                                                 requestUrl As String,
                                                 Optional sessionId As String = Nothing,
                                                 Optional mappingFunc As Func(Of IEnumerable(Of JsonObject), Tout) = Nothing) As Tout
            Dim retVal = GetResultFromCache(Of Tout)(operationName, requestUrl, sessionId)
            If retVal Is Nothing Then
                Dim result = noCacheFunc()
                If mappingFunc IsNot Nothing Then
                    retVal = mappingFunc(result)
                Else
                    retVal = Mapper.Map(Of IEnumerable(Of JsonObject), Tout)(result)
                End If
                SetResultInCache(Of Tout)(operationName, requestUrl, sessionId, retVal)
            End If
            Return retVal
        End Function

    End Class
End Namespace
